package cn.dengta.context.web;

import java.io.IOException;
import java.security.*;
import java.util.UUID;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.dengta.common.context.Context;
import cn.dengta.common.context.Env;
import cn.dengta.common.security.CryptoSuite;
import cn.dengta.common.web.Client;
import cn.dengta.common.web.Ready;
import cn.dengta.webapp.base.util.AppVersions;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.*;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 对部分接口的响应数据进行加密和签名
 */
@Getter
@Setter
@RestControllerAdvice
@Slf4j
public class EncryptResponseBodyAdapter implements ResponseBodyAdvice<Object> {

	CryptoSuiteContext context;

	public EncryptResponseBodyAdapter(CryptoSuiteContext context) {
		this.context = context;
	}

	/**
	 * 该方法用于判断当前请求的返回值，是否要执行 {@link #beforeBodyWrite}
	 *
	 * @param methodParameter handler方法的参数对象
	 * @param converterType 将会使用到的 HTTP 消息转换器类类型
	 */
	@Override
	public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {
		return context.isEnabled();
	}

	/**
	 * 在 HTTP 消息转换器转换之前执行
	 *
	 * @param body 服务端的响应数据
	 * @param methodParameter handler方法的参数对象
	 * @param mediaType 响应的ContentType
	 * @param converterType 将会使用到的Http消息转换器类类型
	 * @param serverHttpRequest serverHttpRequest
	 * @param serverHttpResponse serverHttpResponse
	 * @return 如果返回 null，则表示不输出
	 */
	@Override
	@Nullable
	public Object beforeBodyWrite(@Nullable Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> converterType, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
		if (body != null) {
			HttpServletRequest request = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();

			boolean shouldEncrypt = "1".equals(request.getParameter("encrypt"));
			if (!shouldEncrypt && Env.inTest()
					&& AppVersions.compare(ProxyRequest.getVersion(request.getHeader(Context.internal().getAppIdHeaderName())), AppVersions.v_3_0_0) > 0) {
				final Ready ready = methodParameter.getMethodAnnotation(Ready.class);
				shouldEncrypt = ready != null && ready.encrypt();
			}

			if (shouldEncrypt) {
				Client client = Client.getClient(request); // TODO 要考虑防止前端伪造请求头，可能绕过响应判定
				final CryptoSuite suite = context.getCryptoSuite(client);
				String jsonStr = UserIdEncodeFilter.toJSONString(body);
				HttpServletResponse response = ((ServletServerHttpResponse) serverHttpResponse).getServletResponse();
				final String nonce = UUID.randomUUID().toString();
				final String timestamp = Long.toString(System.currentTimeMillis());
				final char sep = '\n';
				final String source = request.getMethod() + sep + nonce + sep + timestamp + sep + jsonStr;
				String sign;
				try {
					jsonStr = suite.getAes().encrypt(jsonStr);
					sign = suite.getRsa().sign(source);
				} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
					log.error("加密响应数据时出错：", e);
					return body; // 如果报错，直接降级为明文响应
				}
				response.setHeader("Dt-Nonce", nonce);
				response.setHeader("Dt-Timestamp", timestamp);
				response.setHeader("Dt-Sign", sign);
				try {
					response.getWriter().write(jsonStr);
				} catch (IOException e) {
					throw new IllegalStateException(e);
				}
				return null;
			}
		}
		return body;
	}

}
